值传递和值引用
js 数据类型分为 基本类型和引用类型
基本类型: Number Boolean String Undefined Null Symbol
引用类型: Object Function Array Date RegExp Math 其他类 object 的实例对象
基本类型的存储方式: 变量和值都在栈内存中
引用类型的存储方式: 变量名存储在栈内存中,值存储在堆内存中,堆内存中会提供一个引用地址指向堆内存中的值,而这个引用地址是储存在栈内存中的。
打印以下结果
1
2
3
4
5
6
7
8
9function changeObjProperty(o) {
o.siteUrl = "http://www.baidu.com"
o = new Object()
o.siteUrl = "http://www.google.com"
}
let webSite = new Object();
changeObjProperty(webSite);
console.log(webSite.siteUrl);
<!-- 原因:changeObjProperty 的参数是变量的引用, new Object 是创建了一个新对象,对外面的 webSite 不产生影响 -->检查对象类型
1
2
31. typeof
2. instanceof
3. Object.prototype.toString.call(args) // 可检查所有对象类型
深拷贝 & 浅拷贝
这里的拷贝要区别于赋值操作, 对基本类型数据的赋值操作是值传递,对引用类型的数据的赋值操作是引用传递,而浅拷贝是新创建了对象,对第一层数据进行复制,子对象的话则是引用传递
浅拷贝:创建新对象,对第一层属性进行赋值,子对象的话依然是引用传递
深拷贝:创建新对象,对对象和子对象进行完全的复制,被拷贝对象发生任何变化不会影响到新对象举例说明赋值和浅拷贝的区别:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29var obj = {
name: 'near',
age: 18,
language: {
value: 'zh'
}
}
var obj2 = obj
var obj3 = copy(obj)
// 浅拷贝
function copy(obj) {
let newObj = {}
for(let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
obj.name = 'Lisa'
console.log(obj2.name) // 'Lisa' 值引用,指向同一块内存地址,所以会同步改变
console.log(obj3.name) // 'near' 第一层基本数据类型是值传递
obj.language.value = 'en'
console.log(obj2.language.value) // 'en' 子对象是值引用,所以会互相影响
console.log(obj3.language.value) // 'en'
常见的浅拷贝
直接赋值
1
2
3
4let obj = { a: 2 }
let obj2 = obj
obj2.a = 1
obj.a // 1对象解构
- for in 循环赋值
ES6 的 Object.assign
1
2
3
4
5
6let obj = { a: 20, b: { c: 3 } }
let obj2 = Object.assign({}, obj)
obj2.a = 10
obj.a // 20
obj2.b.c = 10
obj.b.c // 10
常见的深拷贝
JSON.parse(JSON.stringify())
对象属性比较简单的时候可以使用,此方法会出现的问题有:
- 值为 undefined 的属性转换后会丢失
- 值为 Symbol 类型的属性在转换后丢失
- 值为 RegExp 对象的属性在转换后变成了空对象
- 值为 函数对象的属性在转换后丢失
- 值为 Date 对象的属性在转换后变成了字符串
- 会抛弃对象的 constructor,所有的构造函数会指向 Object
- 对象的循环引用会抛出错误。
手动实现 deepClone
解决问题:
- 循环引用过多导致爆栈
- 特殊值拷贝属性丢失
- 会抛弃对象的 constructor,所有的构造函数会指向 Object
1 | function deepClone(obj, hash = new WeakMap()) { |
对 this 对象的理解
关于 this 的一些关键性理解:
- this 总是指向函数的直接调用者
- 如果有 new 关键字,this 指向 new 出来的实例对象
- 在事件中,this 指向触发这个事件的对象
- IE 下 attachEvent 中的 this 总是指向全局对象 Window
- 箭头函数中,函数体内的this对象,就是定义时所在作用域的对象,而不是使用时所在的作用域的对象。
- 箭头函数里的 this 如果放在全局中就是 window
1 | function foo() { |
手动实现 bind 函数。
bind 做了什么: 1. 将 this 绑定到当前函数参数中 2. 不立即执行,返回一个函数 3. 函数参数原型上的方法还能执行 4. this 不能在绑定到其他变量上面 5. 待执行函数还可以传递其他参数
1
2
3
4
5
6
7
8
9
10Function.prototype.bind = function(obj) {
let args = [].slice.call(arguments, 1)
var fn = this
var bound = function() {
var argguments = Array.from(argguments)
fn.apply(this.constructor === fn ? this : obj, args.concat(argguments))
}
bound.prototype = fn.prototype
return bound
}
- 手动实现 call / apply 函数
1 | <!-- 简单实现 call --> |
隐式转换
隐式转换规则:
- undefined == null,结果是true。且它俩与所有其他值比较的结果都是false。
- String == Boolean,需要两个操作数同时转为Number。
- String/Boolean == Number,需要String/Boolean转为Number。
- Object == Primitive,需要Object转为Primitive(具体通过valueOf和toString方法)。
1 | var a = [1, 2, 3]; |
事件循环, 宏任务,微任务
打印一下结果 事件循环和异步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
// 打印结果
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
事件循环和异步
微任务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146// setTimeout 和 promise
const tasks = []
const output = (i) => new Promise((reslove) => {
setTimeout(() => {
console.log(i);
reslove();
}, 1000 * i)
});
for(var i = 0; i<5; i++) {
tasks.push(output(i))
}
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(i)
}, 1000)
});
// 0 1 2 3 4 5
// setTimout 和 sleep
setTimeout(() => { console.log(1) }, 1000)
setTimeout(() => { console.log(2) }, 0)
setTimeout(() => {
console.log(3)
setTimeout(() => { console.log(4) }, 0)
}, 800)
function sleep(delay) {
var start = (new Date()).getTime();
while((new Date()).getTime() - start < delay) {
continue;
}
}
sleep(1200)
console.log(5)
for(var i=10;i<=15;i++) {
setTimeout(() => {console.log(i)}, 0)
}
// 5 2 3 1 16*6 4
// setTimeout process.nextTick promise
console.log('1');
async function async1() {
console.log('2');
await async2();
console.log('3');
}
async function async2() {
console.log('4');
}
process.nextTick(function() {
console.log('5');
})
setTimeout(function() {
console.log('6');
process.nextTick(function() {
console.log('7');
})
new Promise(function(resolve) {
console.log('8');
resolve();
}).then(function() {
console.log('9')
})
})
async1();
new Promise(function(resolve) {
console.log('10');
resolve();
}).then(function() {
console.log('11');
});
console.log('12');
// 1 2 4 10 12 5 (3 11) 6 8 7 9
// 宏任务 和 微任务的创建时机
console.log('start');
setTimeout(() => {
console.log('children2');
Promise.resolve().then(() => {
console.log('children3');
})
}, 0);
new Promise(function(resolve, reject) {
console.log('children4');
setTimeout(function() {
console.log('children5');
resolve('children6')
}, 0)
}).then((res) => {
console.log('children7');
setTimeout(() => {
console.log(res);
}, 0)
})
// 执行结果
start
children4
children2
children3
children5
children7
children6
// 说明:在没有 reslove 的时候,不会将 then 创建为微任务
//
const p = function() {
return new Promise((resolve, reject) => {
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 0)
resolve(2)
})
p1.then((res) => {
console.log(res);
})
console.log(3);
resolve(4);
})
}
p().then((res) => {
console.log(res);
})
console.log('end');
// 从上向下执行,p1.then 先进入微任务队列,p.then 后进入,所以结果为:
// 3 ‘end’ 2 4
提升(hoisting)
- var 声明会提升
- 函数声明会首先提升,但是函数表达式不会提升声明
- 声明统一提前,赋值原地不变
- 提升变量阶段: var 是提升声明,还没有被定义; 函数关键字的就已经声明和定义
条件判断语句,没有大括号时,if 条件不满足,则最近的一条语句不执行,if 满足,则后面的条件都会走; 有大括号时,不满足时外部条件都执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115a = 2
var a
console.log(a) // 2
console.log(a) // undefined
var a = 2
function A() { console.log(2) }
var A = function() { console.log(1) }
A()
// 1
var A = function() { console.log(1) }
function A() { console.log(2) }
A()
// 1 此时 A 已经在window 属性下,这属于重名,根据js自上而下执行,此时再次声明会被忽略
A()
var A = function() { console.log(1) }
function A() { console.log(2) }
// 2 函数声明会首先被提升,所以输出 2
A()
function A() { console.log(2) }
var A = function() { console.log(1) }
function A() { console.log(3) }
// 3 重复的函数声明还是可以覆盖前面的,函数表达式不会声明提前
if(true) { function B() { console.log(1) } }
{ var B = function() { console.log(2) } }
B()
// 2 函数声明之后,函数表达式可以改变函数;
console.log(a, b) // undefined undefined 执行时都未定义 所以输出 undefined
var a =12, b ='林一一' // window.b = '林一一'
function foo(){
console.log(a, b) // 此时 b 不带 var 将向上级作用域查找, a 因为函数内部 var 变量提升 为 undefined
var a = b =13
console.log(a, b) // 13 13 window.b = 13, a 此时为函数内部变量
}
foo()
console.log(a, b) // 12 13 此时读取全局变量 a = 12, window.b 有foo 改变,所以为 13
// undefined undefined
// undefined 林--
// 13 13
// 12 13
a = 0
function foo(){
var a =12;
b = '林一一'
console.log('b' in window) // b 没有声明 因此向上级作用域查找 true
console.log(a, b) // 12 '林一一'
}
foo()
console.log(b) // window.b = '林一一'
console.log(a) // window.a = 0
function foo(){
console.log(a)
a =12;
b = '林一一'
console.log('b' in window)
console.log(a, b)
}
foo()
// 抛出 ReferenceError: a is not defined
fn();
console.log(v1);
console.log(v2);
console.log(v3);
function fn(){
var v1 = v2 = v3 = 2019;
console.log(v1);
console.log(v2);
console.log(v3);
}
// if 判断语句下的变量提升
if(!("value" in window)){
var value = 2019;
}
console.log(value); // undefined if 条件语句中无论条件是否成立里面的var或者function 声明被提升
console.log('value' in window); // true if 条件语句中无论条件是否成立里面的变量都会提升
var a=2;
function a() {
console.log(3);
}
console.log(typeof a);
// number 通过 var 声明之后,根据自上而下执行机制,函数声明被忽略
var a = 10;
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)
})()
// undefined 10 20
// 存在两个作用域 全局作用域和匿名函数作用域, 函数作用域内因为有 var ,存在变量名提升,所以先打印 undefined, window.a 输出的是全局变量a,为10
作用域
考察作用域
哪些语法会创建块级作用域:
1. try/catch,catch 分句会创建块及作用域,error 只能在内部调用 2. with 3. let 会将变量绑定在任意作用域中,比如常见的 {} ,let 声明不会提升 4. {} 会创建块及作用域, 里面声明的变量或者函数无论条件是否满足,都会被变量提升
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34for(var i =0; i< 5;i++) {
setTimeout((i) => {console.log(i)}, 0)
}
// 5 5 5 5 5
for(let i =0; i< 5;i++) {
setTimeout((i) => {console.log(i)}, 0)
}
// 0 1 2 3 4
for(var i =0; i< 5;i++) {
(function(i){
setTimeout((i) => {console.log(i)}, 0)
})(i)
}
let i; // 此时的 i 仍声明在全局变量中
for(i=0;i<5;i++) {
setTimeout(() => {console.log(i) }, 0)
}
// 5 5 5 5 5
for(let i=0;i<5;i++) { // let 将 i 绑定到for 循环中,并且是将其重新绑定到每次循环中,所以可以得到 0 1 2 3 4
setTimeout(() => {console.log(i) }, 0)
}
function demo(){
let x = b = 0;
return x
}
demo()
console.log(typeof x) x // undefined 只在函数内部声明,所以外部无法获取
console.log(typeof b) b // number 并没有被声明,所以向上级作用域查找并最终添加到window.b ,var let const 在此场景下功效相同
正则表达式
正则表达式
1
2
3var str = '<p> Hello </p> World <br>!</br>';
var regx = /<[^<>]+>/g
console.log(str.replace(regx, ''));
object 的 key值的类型转换
obj 的 key 如果是变量的话,会转换成 string
1 | var a = {} |
js 方法
考察 array 数组方法
1
2
3
4
5
6
7
8
9
10[1,2,3,4].reduce((x,y) => {console.log(x,y); return x;})
// 输出结果
A: 1 2; 3 3; 6 4;
B: 1 2; 2 3; 3 4;
C: 1 undefined; 2 undefined; 3 undefined; 4 undefined;
D: 1 2; undefined 3; undefined 4;
// reduce 不改变原数组,返回新数组
考察作用域,函数
1
2
3
4
5
6
7
8
9const value = { number: 10 }
const mutlipt = (x = { ...value }) => {
console.log(x.number *= 2)
}
mutlipt()
mutlipt()
mutlipt(value)
mutlipt(value)
考察加减
1
2
3
4
5
6
7
8
9let num = 10
let increaseNum = () => num++;
let increasePassedNum = (number) => number++;
let num1 = increaseNum();
let num2 = increasePassedNum(num1++);
console.log(num1);
console.log(num2);
JSON.stringify 第二个参数
1
2
3
4
5
6
7
8
9
10
11
12
13var a = {
name: 'test',
age: 18,
email: 'qq.com'
}
var b = JSON.stringify(a, ['age', 'email'])
// 解读:
JSON.stringify(value, replacer)
replacer:
如果是函数 则以该函数对所有value做格式化处理
如果是数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
如果该参数为 null 或者未提供,则对象所有的属 性都会被序列化
考察 Object.defineProperty, Object.keys
1
2
3
4
5
6
7var person = { name: 'haha' }
Object.defineProperty(person, 'age', { value: 12 })
console.log(person)
console.log(Object.keys(person))
// Object.keys 返回对象可枚举 enumerable 的属性
// Object.defineProperty 定义的属性默认 enumerable configurable writable 为 false,get set value 为 undefined
考察 delete
1
2
3
4
5
6
7
8const name = '111'
age = 18
console.log(delete name)
console.log(delete age)
// 任何存在或者不存在的属性删除时 返回 true
// 除非删除不可设置的属性时,返回 false ,使用 let / const 声明的变量
// delete 只会删除自身属性 不会删除原型链上的属性
charAt
1
2
3
4
5
6
7
8
9
10
11
12var str = 'aaa we dasd'
console.log(str.charAt(3)) // " "
console.log(str.chatAt(0)) // a
小驼峰字符串:
let capitalize = (value) => {
value = toString(value)
let firstCode = value.charAt(0)
let trailing = value.slice(1)
return firstCode.toUpperCase() + trailing;
}
generator 生成器
1
2
3
4
5
6
7
8
9
10
11
12
13function* generator(i){
yield i
yield i * 2
}
let gen = generator(10);
console.log(gen.next().value);
// 10
console.log(gen.next().value);
// 20
console.log(gen.next().value);
// undefined
set
1
2
3
4
5
6
7
8
9
10// 一维数组去重
Array.from(new Set([1,1,2,2,3]))
// 根据先进先出,获取最先插入的元素
set.entries().next().value[0]
// delete 操作返回值
返回操作前 has 的值,存在当前值则返回 true, 不存在则返 回 false
// 值 大小写敏感并且不能重复
运算符
1
2
3
4
53.1 3. .3 都是合法的数字
// 以下哪些情况会输出 ’3‘
3.toString() // 解析为 (3.)toString()
3..toString() // (3.).toString()
3...toString() // (3..).toString()error 的类型
1
2
3
4
5RangeError: 数值变量或参数超出其有效范围
ReferenceError: 无效的引用,比如变量未定义
SyntaxError: eval()在解析代码的过程中发生的语法错误。
TypeError: 变量或参数不属于有效类型
URIError: 给 encodeURI()或 decodeURl()传递的参数无效。手动书写 Promise.all
1 | function promiseAll (promises) { |